#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <errno.h>

#include "server.h"

/*
buf - buf for send to client
list - global list of active players
listSem - sem for lock global list
myself - pointer to current player

net proto
server -> client
<num objs u8>    <obj desc u8> <obj pos u8> ....

client -> server
<intent u8>
*/

static inline void gameTickProcessor( struct instance* this, char tick ){
	
	int s;
	int res;
	char buf[2];
	char cmd[2];
	struct tanksPlayer* myself;
	struct tanksPlayer* player;
	int tmpX;
	int tmpY;
	
	s = 1;
	this->situation[0] = 0;
	
	
	for( myself = this->players; myself; myself = myself->next ) {
		
		memset( cmd, 0, 2 );
		
		/*********   recv intent   *********/
		
		res = recv( myself->sock, buf, 2, MSG_WAITALL );
		if( res < 0 ){
			if( errno != EAGAIN || errno != EWOULDBLOCK ) perror( "recv intent" );
			goto l_handleIntent;
		}
		
		if( res != 2 ){
			printf( "WARNING: Too few bytes received!\n" );
			goto l_handleIntent;
		}
		
		if( buf[0] == 'g' ) myself->gearbox = buf[1];
		else                memcpy( cmd, buf, 2 );
		
		/*********   handle intent   *********/
l_handleIntent:
		
		//if player spectator
		if( !myself->rot ){
			
			//select spawn point
			if( cmd[0] == 's' ){
				if(       cmd[1] == '1' ){
					myself->rot = 'd';
					myself->posX = 1;
					myself->posY = 10;
				}else if( cmd[1] == '2' ){
					myself->rot = 'd';
					myself->posX = 8;
					myself->posY = 10;
				}else if( cmd[1] == '3' ){
					myself->rot = 'u';
					myself->posX = 1;
					myself->posY = 180;
				}else if( cmd[1] == '4' ){
					myself->rot = 'u';
					myself->posX = 8;
					myself->posY = 180;
				}
				myself->gearbox = 0x00;
			}
			
		//if player alive
		} else {
			
			//if player blasting
			if( myself->blast ){
				if( tick ){
					myself->blast--;
					if( myself->blast ){
						if( myself->rot == 'B' ) myself->rot = 'b';
						else myself->rot = 'B';
					} else {
						myself->rot = 0x00;
					}
				}
				
			//if player send "fire" cmd
			} else if( cmd[0] == 'f' && !myself->shotRot ){
				myself->shotPosX = myself->posX;
				myself->shotPosY = myself->posY;
				myself->shotRot  = myself->rot;
				
			//handle tank move
			} else if( tick && myself->gearbox ) {
				
				tmpX = myself->posX;
				tmpY = myself->posY;
				
				//move up
				if( myself->gearbox == 'u' ){
					
					if( myself->gearbox != myself->rot ) myself->rot = myself->gearbox;
					else if( tmpY > 10 ){
						tmpY -= 10;
						
						//check collision
						for( player = this->players; player; player = player->next ){
							
							if( !player->rot   ) continue;
							if(  player->blast ) continue;
							if(  player->posX != tmpX - 2 && 
							     player->posX != tmpX - 1 &&
							     player->posX != tmpX     &&
							     player->posX != tmpX + 1 &&
							     player->posX != tmpX + 2 ) continue;
							
							if(  player->posY == tmpY - 20 ) goto l_skipMove;
						}
					}
					
				//move left
				}else if( myself->gearbox == 'l' ){
					
					if( myself->gearbox != myself->rot ) myself->rot = myself->gearbox;
					else if( tmpX > 1 ){
						tmpX--;
						
						//check collision
						for( player = this->players; player; player = player->next ){
							
							if( !player->rot   ) continue;
							if(  player->blast ) continue;
							if(  player->posY != tmpY - 20 && 
							     player->posY != tmpY - 10 &&
							     player->posY != tmpY      &&
							     player->posY != tmpY + 10 &&
							     player->posY != tmpY + 20 ) continue;
							
							if(  player->posX == tmpX - 2 ) goto l_skipMove;
						}
					}
					
				//move down
				} else if( myself->gearbox == 'd' ){
					
					if( myself->gearbox != myself->rot ) myself->rot = myself->gearbox;
					else if( tmpY < 180 ){
						tmpY += 10;
						
						//check collision
						for( player = this->players; player; player = player->next ){
							
							if( !player->rot   ) continue;
							if(  player->blast ) continue;
							if(  player->posX != tmpX - 2 && 
							     player->posX != tmpX - 1 &&
							     player->posX != tmpX     &&
							     player->posX != tmpX + 1 &&
							     player->posX != tmpX + 2 ) continue;
							
							if(  player->posY == tmpY + 20 ) goto l_skipMove;
						}
					}
					
				//move right
				}else if( myself->gearbox == 'r' ){
					
					if( myself->gearbox != myself->rot ) myself->rot = myself->gearbox;
					else if( tmpX < 8 ){
						tmpX++;
						
						//check collision
						for( player = this->players; player; player = player->next ){
							
							if( !player->rot   ) continue;
							if(  player->blast ) continue;
							if(  player->posY != tmpY - 20 && 
							     player->posY != tmpY - 10 &&
							     player->posY != tmpY      &&
							     player->posY != tmpY + 10 &&
							     player->posY != tmpY + 20 ) continue;
							
							if(  player->posX == tmpX + 2 ) goto l_skipMove;
						}
					}
				}
				
				myself->posX = tmpX;
				myself->posY = tmpY;
			}//handle tank move
		}//if player alive
		
l_skipMove:
		
		//add myself to situation
		if( myself->rot ){
			this->situation[0]++;
			this->situation[s] = myself->rot;
			s++;
			this->situation[s] = myself->posX + myself->posY;
			s++;
		}
		
		/*********   move shot   *********/
		
		if( myself->shotRot ){
			      if( myself->shotRot == 'u' ){
				if( myself->shotPosY > 0 ) myself->shotPosY -= 10;
				else myself->shotRot = 0x00;
			}else if( myself->shotRot == 'l' ){
				if( myself->shotPosX > 0 ) myself->shotPosX--;
				else myself->shotRot = 0x00;
			}else if( myself->shotRot == 'd' ){
				if( myself->shotPosY < 190 ) myself->shotPosY += 10;
				else myself->shotRot = 0x00;
			}else if( myself->shotRot == 'r' ){
				if( myself->shotPosX < 9 ) myself->shotPosX++;
				else myself->shotRot = 0x00;
			}
			
			//check hit
			for( player = this->players; player; player = player->next ){
				
				if( !player->rot      ) continue;
				if(  player->blast    ) continue;
				if(  player == myself ) continue;
				if(  player->posX != myself->shotPosX && player->posX - 1  != myself->shotPosX && player->posX + 1  != myself->shotPosX ) continue;
				if(  player->posY != myself->shotPosY && player->posY - 10 != myself->shotPosY && player->posY + 10 != myself->shotPosY ) continue;
				
				player->blast = 16;
				player->rot = 'B';
				myself->shotRot = 0x00;
				break;
			}
			
			//add my shot to situation
			if( myself->shotRot ){
				this->situation[0]++;
				this->situation[s] = 'f';
				s++;
				this->situation[s] = myself->shotPosX + myself->shotPosY;
				s++;
			}
		}
	}
	
}

static inline void playerRegister( struct instance* this, struct tanksPlayer* newPlayer ){
	newPlayer->next = this->players;
	this->players = newPlayer;
	this->onlinePlayers++;
}

static inline void playerUnregister( struct instance* this, struct tanksPlayer* delPlayer ){
	
	struct tanksPlayer* player;
	
	if( this->players == delPlayer ) this->players = delPlayer->next;
	else {
		for( player = this->players; player; player = player->next ){
			if( player->next == delPlayer ){
				player->next = delPlayer->next;
				break;
			}
		}
	}
	
	this->onlinePlayers--;
	
}

/**************
func openTcpPort
prepare and open port
in
	port - port No to listen
	
return
	neg on err
	else port fd
***************/

static int openTcpPort( unsigned short port ){
	
	int res;
	int serverSocket;
	const int reuseAddrEnable = 1;
	struct sockaddr_in addr;
	
	//socket
	serverSocket = socket( AF_INET, SOCK_STREAM, 0 );
	if( serverSocket < 0 ){
		perror( "socket" );
		return -2;
	}
	
	//set reuseaddr opt
	res =  setsockopt( serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddrEnable, sizeof(reuseAddrEnable) );
	if( res ){
		perror( "server sock set SO_REUSEADDR" );
		goto l_err;
	}
	
	//mark non block
	res = fcntl( serverSocket, F_SETFL, O_NONBLOCK );
	if( res ){
		perror( "server sock set O_NONBLOCK" );
		goto l_err;
	}
	
	//prepare to bind
	memset ( &addr, 0, sizeof(addr) );
	addr.sin_family = AF_INET;
	addr.sin_port = htons( port );
	addr.sin_addr.s_addr = htonl( INADDR_ANY );
	
	//bind
	res = bind( serverSocket, (struct sockaddr*)&addr, sizeof(addr) );
	if( res < 0 ){
		perror( "bind" );
		goto l_err;
	}
	
	//listen
	res = listen( serverSocket, 5 );
	if( res ){
		perror( "listen" );
		goto l_err;
	}
	
	return serverSocket;
	
l_err:
	close( serverSocket );
	return -2;
}

/**********************************
func acceptPlayer
	accept pending connection, alloc 
	new player and register him

input
	this - server instance
	
return
	0 - ok
	neg on err
*************************************/

static inline int acceptPlayer( struct instance* this ){
	
	int res;
	int sock;
	char buf[8];
	struct tanksPlayer* player;
	
	//max players
	if( this->onlinePlayers > 4 ) return 0;
	
	/***   CONNECT   ***/
	
	//accept
	sock = accept( this->serverSocket, NULL, NULL );
	if( sock < 0 ){
		if( errno == EAGAIN || errno == EWOULDBLOCK ) return 0;
		perror( "accept" );
		close( this->serverSocket );
		return -2;
	}
	
	//mark player sock non block
	res = fcntl( sock, F_SETFL, O_NONBLOCK );
	if( res ){
		perror( "player sock set O_NONBLOCK (not fatal)" );
		close( sock );
		return 0;
	}
	
	/***   HANDSHAKE   ***/
	
	res = read( sock, buf, 5 );
	if( res < 0 ) goto l_drop;
	
	buf[5] = 0x00;
	if( strcmp(buf, "TANKS") ) goto l_drop;
	
	res = write( sock, "OK", 2 );
	if( res != 2 ) goto l_drop;
	
	/***   REGISTER   ***/
	
	//alloc player
	player = malloc( sizeof(*player) );
	if( !player ){
		printf( "ERROR: cannot allocate new player(not fatal).\n" );
		close( sock );
		return 0;
	}
	
	//init player
	memset( player, 0, sizeof(*player) );
	player->sock = sock;
	
	//register player
	playerRegister( this, player );
	
	printf( "Connected %i\n", sock );
	return 0;
	
	
	//handshake failed
l_drop:
	printf( "%i handhake failed\n", sock );
	close( sock );
	return 0;
}

static inline void sendSituation2Players( struct instance* this ){
	
	int res;
	int bytes;
	//unsigned char buf[2];
	struct tanksPlayer* kick;
	struct tanksPlayer* myself;
	
	bytes = (this->situation[0] * 2) + 1;
	myself = this->players;
	
l_next:
	
	if( !myself ) return;
	
	/***   SEND   ***/
	
	res = write( myself->sock, this->situation, bytes );
	if( res < 0 ){
		perror( "send" );
		if( errno == EAGAIN || errno == EWOULDBLOCK ) goto l_iterDone;
		goto l_kick;
		
	}
	
	if( res != bytes ){
		printf( "send err\n" );
		goto l_kick;
	}
	
l_iterDone:
	myself = myself->next;
	goto l_next;
	
l_kick:
	
	kick = myself;
	myself = myself->next;
	
	//del from list
	playerUnregister( this, kick );
	
	//close socket
	close( kick->sock );
	printf( "Disconnected %i\n", kick->sock );
	
	//free()
	free( kick );
	
	goto l_next;
}

static int mainLoop( unsigned short port ){
	
	int res;
	char tick;
	struct instance this;
	struct timeval startTime;
	struct timeval endTime;
	long long int sleepTime;
	
	tick = 0x00;
	
	
	/***   PREPARE   ***/
	//init instance
	memset( &this, 0, sizeof(this) );
	
	//open port
	this.serverSocket = openTcpPort( port );
	if( this.serverSocket < 0 ) return this.serverSocket;
	
	printf( "Listen %hu TCP...\n", port );
	
	/***   WORK PROCESS   ***/
l_work:
	
	//start time
	gettimeofday( &startTime, NULL );
	
	//accept
	res = acceptPlayer( &this );
	if( res ) return -2;
	
	//game tick
	gameTickProcessor( &this, tick );
	tick = ~tick;
	
	//communicate
	sendSituation2Players( &this );
	
	//end time
	gettimeofday( &endTime, NULL );
	
	//sleep
	sleepTime = 40000 - ((endTime.tv_usec+((endTime.tv_sec-startTime.tv_sec)*1000000))-startTime.tv_usec);
	if( sleepTime > 0 ) usleep( sleepTime );
	else printf( "WARNING: overload.\n" );
	
	//continue
	goto l_work;
}

int main( int argc, char* argv[], char* envp[] ){
	
	int i;
	int res;
	unsigned short port;
	
	port = 0;
	
	//ignore sigpipe
	signal( SIGPIPE, SIG_IGN );
	
	//base check
	if( argc < 2 ) {
		l_help:
		printf( "usage:\n\t%s -p <TCP port>\n", argv[0] );
		return 2;
	}
	
	//parse arguments
	for( i = 1; i < argc; i++ ){
		
		if( !strcmp(argv[i], "-p") ){
			i++;
			if( i == argc ) goto l_help;
			res = sscanf( argv[i], "%hu", &port );
			if( res != 1 ) goto l_help;
		}
	}
	
	//check parsed values
	if( !port ) goto l_help;
	
	//start server
	mainLoop( port );
	
	return 0;
}
